<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body {
      background: #eeeeee;
    }

    .box {
      width: 850px;
      height: 600;
      margin: 0 auto;
      position: relative;
    }

    #controls {
      position: absolute;
      left: 27.8%;
      top: 25px;
    }

    #canvas {
      width: 850rpx;
      display: block;
      background: #ffffff;
      cursor: pointer;
      -webkit-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);
      -moz-box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);
      box-shadow: 4px 4px 8px rgba(0, 0, 0, 0.5);
    }

    .actions {
      position: absolute;
      right: 0;
      top: 0;
      width: 120px;
      margin-top: 10px;
    }

    button {
      width: 100px;
      margin-bottom: 10px;
      display: block;
    }
  </style>

</head>

<body>
  <div class="box">
    <canvas id="canvas" width="850" height="600">
    </canvas>
    <div class="actions">
      <button id="wipe">擦拭</button>
      <button id="wipeStop" style="color:red">结束擦拭</button>
      橡皮擦的大小:
      <select id="wipeSize">
        <option value="10">10</option>
        <option value="20">20</option>
        <option value="30">30</option>
        <option value="40">40</option>
        <option value="50">50</option>
      </select>
      <button onclick="remove()">清除</button>
      <button id="cancel">撤销</button>
      <button id="save">保存</button>
    </div>
  </div>
</body>
<script>
  let canvas = document.getElementById('canvas'), //获取canvas元素
    cancel = document.getElementById("cancel");
  save = document.getElementById("save");
  wipe = document.getElementById("wipe"),
    wipeStop = document.getElementById("wipeStop"),
    wipeSize = document.getElementById("wipeSize"),
    size = 10,
    context = canvas.getContext('2d'), //创建绘制上下文
    loc = {
      x: 0,
      y: 0
    },
    isDraw = false, //是否开始绘制
    drawCanavsInner = null,
    saveData = [],
    url = ""
  isWipe = false;
  window.onload = () => {
    wipeStop.style.display = "none";
  }
  const drawGrid = (stepX = 30, stepY = 30, color = 'lightgray') => {
    context.strokeStyle = color;
    context.lineWidth = 0.5;
    // 绘制x轴
    for (let i = stepY + 0.5; i < canvas.height; i += stepY) {
      context.beginPath(); //重置路径
      context.moveTo(0, i); //起点
      context.lineTo(canvas.width, i);
      context.stroke(); //绘制
    }
    // 绘制y轴
    for (let i = stepX + 0.5; i < canvas.width; i += stepX) {
      context.beginPath();
      context.moveTo(i, 0);
      context.lineTo(i, canvas.width);
      context.stroke();
    }
  }
  // 保存当前绘制状态
  const svaeCanvasStatus = () => {
    drawCanavsInner = context.getImageData(0, 0, canvas.width, canvas.height); //从0 0开始保存当前绘制状态
    return drawCanavsInner;
  }
  // 更新当前绘制状态
  const updateDrawCanvas = () => {
    if (drawCanavsInner) {
      context.putImageData(drawCanavsInner, 0, 0, 0, 0, canvas.width, canvas.height); //填充当前
    }
  }
  // 获取canavs内部真实点位（如何画布宽高和真实元素不一致，需要拿到比例去除）
  const canavsDrawMap = (loc) => {
    const {
      top,
      left
    } = canvas.getBoundingClientRect();
    return {
      x: loc.x - left,
      y: loc.y - top
    }
  }

  // 绘制线条，x和y是当前位置
  const drawLine = (x, y) => {
    context.beginPath();
    context.strokeStyle = "#000";
    context.lineWidth = 6;
    context.moveTo(loc.x, loc.y); //起点
    context.lineTo(x, y); //终点
    context.stroke(); //绘制
    context.closePath();
  }

  // 绘制橡皮擦,isStroke是为了保证绘制时，裁剪的区域没有描边，但是裁剪完需要描边，以此告诉用户当前橡皮擦在哪里
  const drawWite = (x, y, isStroke = false) => {
    context.save(); //保存当前绘制状态
    context.beginPath();
    context.strokeStyle = "red";
    context.lineWidth = 1;
    context.arc(x, y, size / 2, 0, Math.PI * 2);
    if (isStroke) {
      context.stroke();
    }
    context.clip(); //裁剪区域
    context.clearRect(0, 0, canvas.width, canvas.height); //清楚裁剪区域
    drawGrid(); //重新填充清除后的网格
    context.restore(); //恢复画布操作区域
    if (!isStroke) {
      saveData.push(svaeCanvasStatus());
    }
  }
  // 鼠标按下事件
  canvas.addEventListener("mousedown", (e) => {
    const {
      x,
      y
    } = e;
    // 鼠标按下真正位置
    loc = canavsDrawMap({
      x,
      y
    });
    isDraw = true;
  }, false);

  // 鼠标移动
  canvas.addEventListener("mousemove", (e) => {
    const {
      x,
      y
    } = canavsDrawMap(e); //鼠标当前位置
    if (isDraw) {
      if (!isWipe) {
        drawLine(x, y);
        loc = {
          x,
          y
        }
      } else {
        updateDrawCanvas();//更新绘制状态
        drawWite(x, y);//绘制不带描边的橡皮擦
        svaeCanvasStatus();//保存当前清楚的状态
        drawWite(x, y, true);//绘制带描边的橡皮擦（此时不保存，方便松开时去除描边的橡皮擦）
      }
    }
  }, false);

  // 鼠标松开
  canvas.addEventListener("mouseup", (e) => {
    if (!isDraw) return;
    isDraw = false;
    const {
      x,
      y
    } = e;
    if (!isWipe) {
      saveData.push(svaeCanvasStatus());//保存当前绘制的状态
    } else {
      updateDrawCanvas();//更新当前清楚后的画布
    }
  }, false);

  drawGrid();

  // 开启擦拭
  wipe.addEventListener("click", () => {
    isWipe = true;
    wipe.style.display = "none";
    wipeStop.style.display = "block";
  }, false);

  // 结束擦拭
  wipeStop.addEventListener("click", () => {
    isWipe = false;
    wipe.style.display = "block";
    wipeStop.style.display = "none";
  }, false);

  // 橡皮擦大小
  wipeSize.addEventListener("change", (e) => {
    size = e.target.value;
  }, false);

  // 清除
  const remove = () => {
    saveData = [];
    context.clearRect(0, 0, canvas.width, canvas.height); //清除画布
    drawGrid();
  }

  // 撤销
  cancel.addEventListener("click", () => {
    console.log(saveData);
    isCancle = true;
    const length = saveData.length;
    context.clearRect(0, 0, canvas.width, canvas.height); //清除画布
    if (length > 1) {
      saveData.pop();
      drawCanavsInner = saveData[saveData.length - 1];
      updateDrawCanvas();
    } else {
      drawGrid();
    }
  }, false);
  // 保存事件
  save.addEventListener("click", () => {
    const url = canvas.toDataURL(); //保存为url
    // 创建一个链接元素
    const link = document.createElement('a');
    link.href = url; // 设置链接的href为data URI
    link.download = 'canvas_image.png'; // 设置下载的文件名
    document.body.appendChild(link); // 将链接添加到DOM树中
    link.click(); // 模拟点击链接
    document.body.removeChild(link); // 点击后移除链接
  }, false);
</script>

</html>